Skip to content

fix(runtime): classify usage-limit failures behind auth statuses as billing - #3660

Merged
Astro-Han merged 1 commit into
apache:mainfrom
yunaremaia:fix/2516-usage-limit-vs-auth
Aug 24, 2026
Merged

fix(runtime): classify usage-limit failures behind auth statuses as billing#3660
Astro-Han merged 1 commit into
apache:mainfrom
yunaremaia:fix/2516-usage-limit-vs-auth

Conversation

@yunaremaia

Copy link
Copy Markdown
Contributor

Summary

Fixes #2516.

Providers report exhausted plan windows, credits, and subscriptions through different evidence channels, and the status-first fallback mislabeled all of them:

  • Some use explicit structured codes — OpenAI's insufficient_quota even arrives as 429, DeepSeek sends insufficient_balance.
  • Some gate the window behind credential-shaped 401/403 statuses for validly signed-in users (the Kimi Code plan case from the issue), which projected to Auth"Authentication failed" — pointing the user at re-authenticating when the useful action is waiting for the window to reset or checking the subscription.

Changes in packages/runtime/src/provider-error-classification.ts, at the shared classifier boundary (no provider-specific branches, no user-facing strings in adapters):

  • New PROVIDER_BILLING_PROVIDER_CODES set, checked with the other structured-code sets before every numeric HTTP fallback — explicit provider evidence outranks the bare status, the same precedence the capacity and overflow sets already follow. This also stops an exhausted quota arriving as 429 from being classified as a transient throttle.
  • The 401/403 fallback now consults USAGE_LIMIT_TEXT_PATTERNS over the composite text: quota / usage-limit / plan / credit-exhaustion wording projects to ProviderBilling instead of Auth. Plain invalid-key and permission messages carry none of that vocabulary and stay Auth.
  • No retry-policy change needed: ProviderBilling already maps to a non-retryable policy in providerRetryMetadata, so closed plan windows stop being retried blindly while transient throttles keep their RateLimit path.
  • Redaction boundary unchanged — only the classification layer is touched; no raw bodies or tokens are surfaced anywhere new.

Verification

Local run this time (npm ci unblocked by pointing the six Azure DevOps mirror URLs in package-lock.json at their identical public npm packages; lockfile restored before commit):

  • npm run build -w @maka/core && npm run build -w @maka/storage && npm run build -w @maka/mcp && npm run build -w @maka/runtime — all pass
  • node --test dist/__tests__/provider-error-classification.test.js15 tests, 15 pass, 0 fail
  • Bite check: reverting only the implementation commit hunk makes the two new billing-projection tests fail (AuthProviderBilling) while the non-regression pins keep passing, then green again with the fix restored

New tests cover: structured usage-limit codes across 401/403/429 (+ non-retryable metadata), plan-window wording through both SDK carriers (error message and raw response body after a schema-parse failure), and genuine invalid-key / permission failures staying Auth.

One scope note: 429 responses whose text suggests a long cap but that carry no structured code still classify as RateLimit — distinguishing those would need per-provider cap wording I can't verify offline, and guessing risked false billing positives on real throttles.

…illing

Fixes apache#2516.

Providers report exhausted plan windows, credits, and subscriptions
through different evidence channels: some use explicit structured codes
(OpenAI insufficient_quota arrives even as 429; DeepSeek sends
insufficient_balance), and some gate the window behind credential-shaped
401/403 statuses for validly signed-in users, which the status-first
fallback projected to 'Authentication failed' — pointing the user at
re-authenticating when the useful action is waiting for the window to
reset or checking the subscription.

- New PROVIDER_BILLING_PROVIDER_CODES set checked with the other
  structured-code sets, before every numeric HTTP fallback: explicit
  provider evidence outranks the bare status (the same precedence the
  capacity and overflow sets already follow).
- The 401/403 fallback now consults USAGE_LIMIT_TEXT_PATTERNS over the
  composite text: quota/usage-limit/plan/credit-exhaustion wording
  projects to ProviderBilling instead of Auth. Plain invalid-key and
  permission messages carry none of that vocabulary and stay Auth.
- ProviderBilling already maps to a non-retryable policy in
  providerRetryMetadata, so closed plan windows stop being retried
  blindly while transient throttles keep their RateLimit path.

Tests: classifier matrix for structured codes across 401/403/429,
plan-window wording via both SDK carriers (error message and raw
response body after a schema-parse failure), and non-regression pins
for genuine invalid-key and permission failures.

Signed-off-by: Yunare Maia <yunare@gmail.com>

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#3660 f9b0dd8 — review (bind exact head)

Gate: CI test success on this head (run 32682534889, check_runs 1/1 success after approval). Fork PR previously blocked on action_required.

Verdict: GO (no P0-P2)

Classification narrowly fixes usage-limit behind 401/403: structured billing codes outrank status, and 401/403 fallback consults usage-limit text patterns over composite text, preserving genuine Auth. Non-retryable billing via providerRetryMetadata correct. Tests cover matrix.

Coexistence note: #2521 overlaps same defect file; #3660 is minimal fix shape. Epoch handling for any follow-up: rebase to current main and take strictly greater than base (do not hardcode number).

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at f9b0dd835713.

Gate at this exact head: hosted test is terminal green — note that this PR's checks had never run at all until the fork workflow was released (32682534889), so the earlier empty check-runs was "not run", not "passed". Zero review threads, no APPROVED review bound to any older commit, and the branch is mergeable.

The change stays inside the file that owns the defect: packages/runtime/src/provider-error-classification.ts and its test, +109/-1, classifying usage-limit failures behind the auth status rather than collapsing them into a generic auth error.

Worth recording for whoever handles this next: #2521 fixes the same defect (both branches are named for issue 2516, and both edit this same file and test), but does it across 46 files and +4428/-173. That one is separately marked NO-GO. If this lands first, #2521 will need to be rebased and reduced to whatever remains genuinely unaddressed.

Approval only; merging is a human's call.

@Astro-Han
Astro-Han merged commit 79fa4ac into apache:main Aug 24, 2026
1 check passed
@Astro-Han

Copy link
Copy Markdown
Contributor

LGTM — merged. Thanks for the contribution!

中文

已合并,感谢贡献。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(runtime): distinguish usage limits from authentication failures

2 participants